#include <Windows.h>
#include <stdio.h>
#include <assert.h>
#include "CGame.h"

// core engine
#include "core/CDirect3D.h"
#include "core/CRetroInput.h"

// states
#include "gamestates/CStateMachine.h"
#include "gamestates/CMainMenuState.h"


CGame* CGame::m_pInstance = NULL;
CGame* CGame::GetInstance()
{
	if(m_pInstance == NULL)
	{
		m_pInstance = new CGame();
	}
	return m_pInstance;
}
void CGame::DestroyInstance()
{
	delete m_pInstance;
	m_pInstance = NULL;
}
CGame::CGame(void)
{
	m_bRunning = false;
	m_nFPS = 0;
	m_dblFrameRenderTime = 0.0f;
	m_dblPrevElapsedTime = 0.0;
	m_nWindowWidth = 0;
	m_nWindowHeight = 0;
	m_hWnd = 0;
	m_hInstance = 0;
	
	// pointers
	m_pFunctionCalls = NULL;
	m_pRender = NULL;
	m_pInput = NULL;
	m_pStateMachine = NULL;
}

CGame::~CGame(void)
{
}

void CGame::InitSettings()
{
	m_Settings.invertX = false;
	m_Settings.invertY = false;
	m_Settings.sensitivityX = 20.0f;
	m_Settings.sensitivityY = 20.0f;
}

bool CGame::Initialize(HWND hWnd, HINSTANCE hInstance, int nWindowWidth, int nWindowHeight)
{
	// get singletons
	m_pRender = CDirect3D::GetInstance();
	m_pInput = CRetroInput::GetInstance();
	m_pStateMachine = CStateMachine::GetInstance();


	// setup the rendering
	m_nWindowWidth = nWindowWidth;;
	m_nWindowHeight = nWindowHeight;
	m_hWnd = hWnd;
	m_hInstance = hInstance;
	m_pRender->InitDirect3D(hWnd, m_nWindowWidth, m_nWindowHeight, true, false); // windowed & no vsync
	
	// setup input
	// NOTE: add actions in "ButtonAction" enum at CRetroInput.h line 14
	m_pInput->Init(hWnd, hInstance, RZ_KEYBOARD | RZ_MOUSE | RZ_WIIMOTE, RZ_KEYBOARD);
	m_pInput->RegisterAction(ACTION_UP,		RZ_KEYBOARD, BTN_HELD, DIK_W);
	m_pInput->RegisterAction(ACTION_DOWN,	RZ_KEYBOARD, BTN_HELD, DIK_S);
	m_pInput->RegisterAction(ACTION_LEFT,	RZ_KEYBOARD, BTN_HELD, DIK_A);
	m_pInput->RegisterAction(ACTION_RIGHT,	RZ_KEYBOARD, BTN_HELD, DIK_D);
	m_pInput->RegisterAction(ACTION_FIRE,	RZ_MOUSE, BTN_PRESSED, MOUSE_LEFT);
	m_pInput->RegisterAction(ACTION_UP,		RZ_WIIMOTE, BTN_HELD,  WII_BTN_RIGHT);
	m_pInput->RegisterAction(ACTION_DOWN,	RZ_WIIMOTE, BTN_HELD,  WII_BTN_LEFT);
	m_pInput->RegisterAction(ACTION_LEFT,	RZ_WIIMOTE, BTN_HELD,  WII_BTN_UP);
	m_pInput->RegisterAction(ACTION_RIGHT,	RZ_WIIMOTE, BTN_HELD,  WII_BTN_DOWN);
	m_pInput->RegisterAction(ACTION_FIRE,	RZ_WIIMOTE, BTN_PRESSED, WII_BTN_TWO);
	m_pInput->RegisterAction(ACTION_CENTER_CURSOR,	RZ_WIIMOTE, BTN_HELD,  WII_BTN_A);

	SetCursorDevicePriority(RZ_MOUSE);
	CenterCursorPosition();

	// setup the states
	m_pStateMachine->Initialize();
	m_pStateMachine->PushState(new CGameState());

	// setup game settings
	InitSettings();

	// add debug functions
	AddFunctionCall(CGame::ShowFPS, this);
	AddFunctionCall(CGame::ShowCursorPosition, this);

	// success
	m_bShowFPS = true;
	m_bRunning = true;
	m_Timer.Start();
	return true;
}

void CGame::Shutdown()
{
	m_Timer.Stop();
	FlushFunctionCalls();

	m_pStateMachine->FlushStates();
	m_pStateMachine->Shutdown();
	m_pStateMachine->FreeInstance();

	m_pInput->Shutdown();
	m_pInput->FreeInstance();
	m_pRender->ShutdownDirect3D();
}

void CGame::Update()
{
	// get the change of time
	double dblCurrentElapsedTime = 0.0;
	dblCurrentElapsedTime = m_Timer.GetElapsedTime();
	m_dblFrameRenderTime = dblCurrentElapsedTime - m_dblPrevElapsedTime;
	m_dblPrevElapsedTime = dblCurrentElapsedTime;
	m_nFPS = m_Timer.FPS();


	// one-a-frame calls
	m_pInput->Update();

	// update teh cursor
	UpdateCursorFunction(this);
	if(m_pInput->CheckAction(ACTION_CENTER_CURSOR))
	{
		CenterCursorPosition();
	}

	// testing for niao
	// calibrate the motion+
	if(m_pInput->KeyPressed(DIK_SPACE))
	{
		CStateMachine::GetInstance()->PushState(new CMotionPlusCalibrateState());
	}
	
	// update the states
	m_pStateMachine->Input(m_pInput);
	m_pStateMachine->Update(static_cast<float>(m_dblFrameRenderTime));

	// Render Start //
	m_pRender->Clear();
	m_pRender->DeviceBegin();
	m_pRender->SpriteBegin();

	// render the states
	m_pStateMachine->Render(m_pRender);

	// call available debug functions
	CallFunctionCalls();

	m_pRender->SpriteEnd();
	m_pRender->DeviceEnd();
	m_pRender->Present();
	// Render End //

}

void CGame::AddFunctionCall(GameFunctor function, void* pArgument)
{
	assert(function != NULL && "cannot pass a NULL function");

	// add function call to top
	FunctionCallList* pFunc = new FunctionCallList();
	pFunc->function = function;
	pFunc->pArgument = pArgument;
	pFunc->pNext = m_pFunctionCalls;
	m_pFunctionCalls = pFunc;
}

void CGame::FlushFunctionCalls()
{
	FunctionCallList* pFunctionCall = NULL;
	while(m_pFunctionCalls != NULL)
	{
		pFunctionCall = m_pFunctionCalls->pNext;
		delete m_pFunctionCalls;
		m_pFunctionCalls = pFunctionCall;
	}
}

void CGame::CallFunctionCalls()
{
	FunctionCallList* pFunctionCall = m_pFunctionCalls;
	while(pFunctionCall != NULL)
	{
		pFunctionCall->function(pFunctionCall->pArgument);
		pFunctionCall = pFunctionCall->pNext;
	}
}

void CGame::ShowFPS(void* pGame)
{
	CGame* Game = (CGame*)pGame;
	// display the FPS and Frame Render Time
	static char szTitleBuffer[256] = {0};
	memset(szTitleBuffer, 0, 256);
	sprintf_s(szTitleBuffer, 256, "FPS(%d) FRT(%f ms)", Game->m_nFPS, Game->m_dblFrameRenderTime * 1000.0);
	Game->m_pRender->DrawTextA(szTitleBuffer, 0, 0, 255, 0, 0);
}

void CGame::ShowCursorPosition(void* pGame)
{
	char posBuffer[256] = {0};
	sprintf_s(posBuffer, 256, "X: %f    Y: %f", ((CGame*)pGame)->m_vfCursorPosition.x, ((CGame*)pGame)->m_vfCursorPosition.y);
	((CGame*)pGame)->m_pRender->DrawTextA(posBuffer, 0, 15, 0, 255, 0);
}

void CGame::ShowAccelerometer(void* pGame)
{
	vec3<float> accelerometer = ((CGame*)pGame)->m_pInput->GetWiiAccelerometerDelta();
	char posBuffer[256] = {0};	
	sprintf_s(posBuffer, 256, "Accelerometer: X: %f    Y: %f    Z: %f", accelerometer.x, accelerometer.y, accelerometer.z);
	((CGame*)pGame)->m_pRender->DrawTextA(posBuffer, 0, 30, 0, 255, 0);
}

void CGame::ShowMotionPlus(void* pGame)
{
	vec3<float> motionplus = ((CGame*)pGame)->m_pInput->GetWiiMotionPlusDelta();
	char posBuffer[256] = {0};	
	sprintf_s(posBuffer, 256, "MotionPlus   : X: %f    Y: %f    Z: %f", motionplus.x, motionplus.y, motionplus.z);
	((CGame*)pGame)->m_pRender->DrawTextA(posBuffer, 0, 45, 0, 255, 0);
}

void CGame::ShowWiiButtonBits(void* pGame)
{
	char wiimoteBits[17] = {0};
	for(int i = 0; i < 16; ++i)
	{
		if(CRetroInput::GetInstance()->m_wiimoteState & (1 << i))
		{
			wiimoteBits[i] = '1';
		}
		else
		{
			wiimoteBits[i] = '0';
		}
	}
	((CGame*)pGame)->m_pRender->DrawTextA(wiimoteBits, 0, 60, 0, 255, 0);
}

void CGame::CursorByMouse(void* pGame)
{
	CGame* game = (CGame*)pGame;
	/* if fullscreen 
	vec3<long>& mouse = game->m_pInput->GetMouseDelta();
	
	game->m_vfCursorPosition.x +=  mouse.x * game->m_Settings.sensitivityX * game->m_dblFrameRenderTime;
	game->m_vfCursorPosition.y +=  mouse.y * game->m_Settings.sensitivityY * game->m_dblFrameRenderTime;
	*/
	POINT ptMouse;
	GetCursorPos(&ptMouse);
	ScreenToClient(game->m_hWnd, &ptMouse);

	game->m_vfCursorPosition.x = static_cast<float>(ptMouse.x);
	game->m_vfCursorPosition.y = static_cast<float>(ptMouse.y);
}

void CGame::CursorByWiimote(void* pGame)
{
	CGame* game = (CGame*)pGame;
	vec3<float>& motionplus = game->m_pInput->GetWiiMotionPlusDelta();
	
	game->m_vfCursorPosition.x += static_cast<float>(static_cast<int>(motionplus.y * game->m_Settings.sensitivityX * static_cast<float>(game->m_dblFrameRenderTime) * 1000.0f) >> 1) / 500.0f;
	game->m_vfCursorPosition.y += static_cast<float>(static_cast<int>(motionplus.z * game->m_Settings.sensitivityY * static_cast<float>(game->m_dblFrameRenderTime) * 1000.0f) >> 1) / 500.0f;
}

void CGame::CenterCursorPosition()
{
	m_vfCursorPosition.x = static_cast<float>(m_nWindowWidth) / 2;
	m_vfCursorPosition.y = static_cast<float>(m_nWindowHeight) / 2;
}

void CGame::SetCursorDevicePriority(BYTE deviceCode)
{
	if(deviceCode == RZ_MOUSE)
	{
		UpdateCursorFunction = CGame::CursorByMouse;
	}
	else if(deviceCode == RZ_WIIMOTE)
	{
		UpdateCursorFunction = CGame::CursorByWiimote;
	}
	else
	{
		assert(false && "Invalid device code!");
	}
}